aboutsummaryrefslogtreecommitdiff
path: root/website/src/pages/blog/[slug].astro
blob: 48cba85737f2c7a6f2bcd06a43b2a873e9285538 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
---
import { getCollection, render } from 'astro:content';
import MarketingLayout from '../../components/MarketingLayout.astro';
import AuthorCard from '../../components/AuthorCard.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts
    .filter((post) => !post.data.draft)
    .map((post) => {
      const slug = post.id.replace(/\.(md|mdx)$/i, '');
      return {
        params: { slug },
        props: { post, slug },
      };
    });
}

const { post, slug } = Astro.props;
const { Content, headings } = await render(post);
const sectionHeadings = headings.filter((heading) => heading.depth === 2);
---

<MarketingLayout
  title={`${post.data.title} — Herdr blog`}
  description={post.data.description}
  canonicalPath={`/blog/${slug}/`}
  ogType="article"
  ogImage={post.data.ogImage}
  ogImageAlt={post.data.ogImage ? post.data.title : undefined}
>
  <main class="content-shell">
    <div class="article-layout section">
      <aside class="article-toc" aria-label="Article sections">
        <p>Sections</p>
        <nav>
          {sectionHeadings.map((heading) => (
            <a href={`#${heading.slug}`} data-heading-link={heading.slug}>{heading.text}</a>
          ))}
        </nav>
      </aside>

      <article class="article-shell">
        <header class="article-header">
          <p class="post-meta">
            <a href="/blog/">Blog</a>
            <time datetime={post.data.date.toISOString()}>{post.data.date.toLocaleDateString('en', {
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            })}</time>
          </p>
          <h1>{post.data.title}</h1>
          <p>{post.data.description}</p>
        </header>
        <div class="article-prose">
          <Content />
        </div>
        <AuthorCard />
      </article>
    </div>
  </main>

  <script>
    const headings = Array.from(document.querySelectorAll('.article-prose h2[id]'));
    const links = new Map(
      Array.from(document.querySelectorAll('[data-heading-link]')).map((link) => [
        link.getAttribute('data-heading-link'),
        link,
      ]),
    );

    function setActive(id) {
      for (const link of links.values()) link.removeAttribute('aria-current');
      links.get(id)?.setAttribute('aria-current', 'true');
    }

    if (headings.length > 0 && links.size > 0) {
      setActive(headings[0].id);
      const observer = new IntersectionObserver(
        (entries) => {
          const visible = entries
            .filter((entry) => entry.isIntersecting)
            .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
          if (visible?.target instanceof HTMLElement) setActive(visible.target.id);
        },
        { rootMargin: '-18% 0px -68% 0px', threshold: 0 },
      );
      for (const heading of headings) observer.observe(heading);
    }
  </script>
</MarketingLayout>